package com.anlogic.sdk.app.handlers;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.logging.log4j.Logger;
import org.eclipse.cdt.core.CCProjectNature;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.core.settings.model.extension.CConfigurationData;
import org.eclipse.cdt.core.templateengine.process.ProcessFailureException;
import org.eclipse.cdt.managedbuilder.buildproperties.IBuildProperty;
import org.eclipse.cdt.managedbuilder.core.BuildException;
import org.eclipse.cdt.managedbuilder.core.IBuilder;
import org.eclipse.cdt.managedbuilder.core.IConfiguration;
import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager;
import org.eclipse.cdt.managedbuilder.internal.core.Configuration;
import org.eclipse.cdt.managedbuilder.internal.core.ManagedBuildInfo;
import org.eclipse.cdt.managedbuilder.internal.core.ManagedProject;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.swt.widgets.Display;

import com.anlogic.sdk.app.Constants;
import com.anlogic.sdk.app.model.ApplicationModel;
import com.anlogic.sdk.app.model.ApplicationProject;
import com.anlogic.sdk.app.model.ApplicationProjectNature;
import com.anlogic.sdk.app.model.IApplicationProject;
import com.anlogic.sdk.tools.IBspTool;
import com.anlogic.sdk.tools.utils.ToolsUtils;
import com.anlogic.sdk.utils.LogUtils;
import com.anlogic.sdk.utils.ProjectUtils;
import com.anlogic.sdk.utils.options.ArchType;
import com.anlogic.sdk.utils.options.ChipType;
import com.anlogic.sdk.utils.options.LanguageType;
import com.anlogic.sdk.utils.options.OsType;
import com.anlogic.sdk.utils.toolchain.ToolchainUtils;

@SuppressWarnings("restriction")
public class ApplicationProjectCreator {
	private static Logger logger = LogUtils.getLogger(ApplicationProjectCreator.class);
	
	public ApplicationProjectCreator() {
	}
	
	public IApplicationProject createApplicationProject(ApplicationModel model, IProgressMonitor monitor) {
		IProject project = null;
		SubMonitor subMonitor = SubMonitor.convert(monitor, 20);
		try {
			logger.info("Create Application Project, run createCDTProject");
			project = createCDTProject(model.getProjName(), model.getProjLoc(), LanguageType.isCpp(model.getLang()), subMonitor.split(10));
		} catch (CoreException e) {
			logger.error("Create Application Project Error while running createCDTProject {}", e.getMessage());
		}
		
		logger.info("Create Application Project, run createApplicationProject");
		IApplicationProject AppProj = null;
		if (OsType.isLinux(model.getOs())) {
			AppProj = ApplicationProject.createApplicationProject(project, "", model.getLang());
		} else {
			AppProj = ApplicationProject.createApplicationProject(project, model.getBspName(), model.getLang());
		}
		
		logger.info("Create Application Project, run preProcessor");
		preProcessor(AppProj, model);
		
		if (model.getPlatformProject() != null && !OsType.isLinux(model.getOs())) {
			logger.info("Create Application Project, run addProjectReferences");
			addProjectReferences(project, model.getPlatformProject());
		}
		
		logger.info("Create Application Project, run generateApp");
		generateApp(subMonitor.split(10), AppProj.getIProject(), model.getPlatformProject(), model.getChip(), model.getAppName());
		logger.info("Create Application Project done");
		return AppProj;
	}
	
	private IProject createCDTProject(String projectName, String location, boolean isCpp, IProgressMonitor monitor) throws CoreException {
		
		IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
		
		IProjectDescription description = ResourcesPlugin.getWorkspace().newProjectDescription(projectName);
		String[] natures = new String[1];
		natures[0] = ApplicationProjectNature.getId();
		description.setNatureIds(natures);
		
		IPath projectLocation = new Path(location);
		if (location != null && !location.trim().isEmpty()) {
			description.setLocation(projectLocation);
		}

		CCorePlugin.getDefault().createCDTProject(description, project, monitor);
		
		if (!project.isOpen()) {
			project.open(monitor);
		}
		
		if (isCpp) {
			CCProjectNature.addCCNature(project, monitor);
		}
		
		return project;
	}
	
	private void setConfigurations(IProject project, String os, String arch, boolean managedProject, IConfiguration[] configs) throws CoreException {
		String PROPERTY = "org.eclipse.cdt.build.core.buildType";
		String PROP_VAL = PROPERTY + ".debug";
		String artifactExtension = "elf";

		CoreModel coreModel = CoreModel.getDefault();
		ICProjectDescription des = coreModel.createProjectDescription(project, false);
		ManagedBuildInfo info = ManagedBuildManager.createBuildInfo(project);
		ManagedProject newManagedProject = new ManagedProject(project, configs[0].getProjectType());
		info.setManagedProject(newManagedProject);

		Map<IConfiguration, IConfiguration> original2newConfigs = new HashMap<>();
		ICConfigurationDescription active = null;
		for (IConfiguration cfg : configs) {
			if (cfg != null) {
				String id = ManagedBuildManager.calculateChildId(cfg.getId(), null);
				Configuration configuration = new Configuration(newManagedProject, (Configuration) cfg, id, false, true);
				CConfigurationData data = configuration.getConfigurationData();
				ICConfigurationDescription cfgDes = des.createConfiguration(ManagedBuildManager.CFG_DATA_PROVIDER_ID, data);
				configuration.setConfigurationDescription(cfgDes);
				configuration.exportArtifactInfo();
				configuration.setArtifactExtension(artifactExtension);
				original2newConfigs.put(cfg, configuration);

				IBuilder builder = configuration.getEditableBuilder();
				
				if (builder != null) {
					builder.setParallelBuildOn(true);
				}
				
				if (builder != null && !managedProject && !OsType.isLinux(os)) {
					builder.setManagedBuildOn(false);
					builder.setBuildPath("${workspace_loc:${ProjName}}");
					builder.setBuildAttribute(IBuilder.BUILD_TARGET_INCREMENTAL, "all TARGET=${ProjName} SDK_ROOT=${bsp_loc}");
					builder.setBuildAttribute(IBuilder.BUILD_TARGET_CLEAN, "clean TARGET=${ProjName} SDK_ROOT=${bsp_loc}");
					if (arch.equals(ArchType.AArch32.getName())) {
						builder.setBuildAttribute(IBuilder.BUILD_TARGET_INCREMENTAL, "all TARGET=${ProjName} SDK_ROOT=${bsp_loc} ARMv8_STATE=32");
						builder.setBuildAttribute(IBuilder.BUILD_TARGET_CLEAN, "clean TARGET=${ProjName} SDK_ROOT=${bsp_loc} ARMv8_STATE=32");
					}
				}

				configuration.setName(cfg.getName());
				configuration.setArtifactName(newManagedProject.getDefaultArtifactName());

				IBuildProperty buildProperty = configuration.getBuildProperties().getProperty(PROPERTY);
				if (buildProperty != null && buildProperty.getValue() != null && PROP_VAL.equals(buildProperty.getValue().getId())) {
					active = cfgDes;
				} else if (active == null) {
					active = cfgDes;
				}
			}
		}

		if (active != null) {
			active.setActive();
		}
		coreModel.setProjectDescription(project, des);

		info.setValid(true);
		//luocheng.lei add a row
		if (managedProject) {
			ProjectUtils.setHiddenCCompiler(project);
		}
		ManagedBuildManager.saveBuildInfo(project, true);
	}
	
	private IProjectHandler getIProjectHandler(String chipName) {
		IProjectHandler projHandler = null;
		if (ChipType.isArm(chipName)) {
			projHandler = new ArmProjectHandler();
		} else {
			projHandler = new RiscvProjectHandler();
		}
		return projHandler;
	}
	
	private void preProcessor(IApplicationProject appProj, ApplicationModel model) {
		String chipName = model.getChip();
		String procType = model.getProc();
		String bitWidth =  model.getBitWidth();
		String arch = ChipType.getArch(chipName, bitWidth).getName();
		String projectType = Constants.getProjectType(chipName);
		boolean managedBuildOn = model.getManagedBuildOn();
		String appName = model.getAppName();
		String os = model.getOs();
		IProject platProj = model.getPlatformProject();
		
		IProject project = appProj.getIProject();
		IProjectHandler projHandler = getIProjectHandler(chipName);
		try {
			setConfigurations(project, os, arch, managedBuildOn, projHandler.createConfigurations());
		} catch (CoreException e) {
			logger.error("Create Application Project Error while setting application project configurations {}", e.getMessage());
		}
		
		projHandler.setProjectSettings(project, projectType, os, arch);
		projHandler.setProjectToolchain(project, projectType, os, arch);
		projHandler.setLaunchConfiguration(project, platProj, projectType, os, arch, chipName, procType);
		
		String toolchainPath = ToolchainUtils.getToolchainPath(os, arch, projectType);
		String toolchainPrefix = ToolchainUtils.getToolchainPrefix(os, arch, projectType);
		
		Map<String, String> vars = new HashMap<String, String> ();
		if (!OsType.isLinux(model.getOs())) {
			vars.put("bsp_loc", String.format("${workspace_loc:%s}", platProj.getName()));
		}
		vars.put("COMPILE_PREFIX", toolchainPath + "/" + toolchainPrefix);
		ProjectUtils.setBuildVariables(project, vars);
		Display.getDefault().asyncExec(new Runnable() {
			public void run() {
				IBspTool bspTool = ToolsUtils.getBspTool(chipName);
				try {
					for (IConfiguration config : projHandler.getConfigurations(project)) {
						projHandler.setToolchainOptions(bspTool, project, platProj, appName, chipName, os, arch, config);
					}
				} catch (BuildException | ProcessFailureException e) {
					logger.error("Create Application Project Error while setting application project configurations {}", e.getMessage());
				}
			}
		});
		
		ManagedBuildManager.saveBuildInfo(project, true);
		if (!OsType.isLinux(model.getOs())) {
			for (IConfiguration config : projHandler.getConfigurations(project)) {
				projHandler.updatePreBuildStep(config);
			}
		}
	}
	
	private void postProcessor(IProject project, String chipName) {
		IProjectHandler projHandler = getIProjectHandler(chipName);
		for (IConfiguration config : projHandler.getConfigurations(project)) {
			if (config.isManagedBuildOn()) {
				File makefile = new File(project.getFile("Makefile").getLocation().toOSString());
				if (makefile.exists()) {
					makefile.delete();
				}
				break;
			}
		}
	}
	
	public void addProjectReferences(IProject project, IProject platProj) {
		try {
			IProjectDescription descripton = project.getDescription();
			List<IProject> referencedProjects = new ArrayList<>();
			referencedProjects.addAll(Arrays.asList(descripton.getReferencedProjects()));
			if (platProj != null) {
				referencedProjects.add(platProj);
			}
			descripton.setReferencedProjects(referencedProjects.<IProject>toArray(new IProject[0]));
			if (project.exists() && project.isAccessible())
				project.setDescription(descripton, null); 
		} catch (CoreException e) {
			logger.error("Create Application Project Error while updating project references {}", e.getMessage());
		}
	}
	
	public void generateApp(IProgressMonitor monitor, IProject project, IProject platProj, String chipName, String appName) {
		Display.getDefault().asyncExec(new Runnable() {
			public void run() {
				IBspTool bspTool = ToolsUtils.getBspTool(chipName);
				String platLoc = (platProj == null) ? "unknown" : platProj.getLocation().toString();
				bspTool.generateApp(platLoc, appName, project.getLocation().toString());
				postProcessor(project, chipName);
				try {
					project.refreshLocal(2, monitor);
				} catch (CoreException e) {
					logger.error("Create Application Project Error while refreshing newly created application project {}", e.getMessage());
				}
			}
		});
	}
	
	public void deleteProject(String projName) {
		try {
			IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projName);
			if (project.exists()) {
				project.delete(true, null);
			}
		} catch (CoreException e) {
			logger.error("Create Application Project Error while deleting newly created application project {}", e.getMessage());
		}
	}
}
